#include "../comm/util.hpp"
#include "../comm/log.hpp"

namespace ns_run
{
    using namespace ns_util;
    using namespace ns_log;

    static void SetRLimit(rlim_t cpu_limt, rlim_t mem_limt)
    {
        struct rlimit _cpu;
        _cpu.rlim_max = RLIM_INFINITY; // 硬限制无穷
        _cpu.rlim_cur = cpu_limt;
        setrlimit(RLIMIT_CPU, &_cpu);

        struct rlimit _mem;
        _mem.rlim_max = RLIM_INFINITY;
        _mem.rlim_cur = mem_limt * 1024;
        setrlimit(RLIMIT_AS, &_mem);
    }

    class Runner
    {
    public:
        Runner(){};
        ~Runner(){};

        /**
         * 运行程序
         * 参数：只需要传递程序名称，不需要带 路径和后缀
         * 内部自动添加 路径和后缀
         *
         *
         * 进程创建 + 替换：让子进程去运行程序
         *
         *
         * 返回值 > 0 进程收到信号异常退出
         * 返回值 == 0 进程正常运行结束
         * 返回值 < 0 进程内部错误
         *
         *
         * 补充参数：时间限制、空间限制（KB)
         *
         */
        static int Run(std::string proc_name, rlim_t cpu_limit, rlim_t mem_limit)
        {
            pid_t pid = fork();

            std::string _stdin = PathUtil::Stdin(proc_name);
            std::string _stdout = PathUtil::Stdout(proc_name);
            std::string _stderr = PathUtil::Stderr(proc_name);

            umask(0);
            int infd = open(_stdin.c_str(), O_CREAT | O_WRONLY, 0644);
            int outfd = open(_stdout.c_str(), O_CREAT | O_WRONLY, 0644);
            int errfd = open(_stderr.c_str(), O_CREAT | O_WRONLY, 0644);

            if(infd < 0 || outfd < 0 || errfd < 0)
            {
                LOG(ERROR) << "运行时打开文件流失败" << std::endl;
                return -1;
            }

            if (pid < 0)
            {
                LOG(ERROR) << "运行时创建子进程失败" << std::endl;
                close(infd);
                close(outfd);
                close(errfd);
                return -2;
            }

            /**
             * 程序运行有三种结果：
             * 运行成功，结果正确
             * 运行成功，结果不正确
             * 运行失败，程序异常
             */

            /**
             * 一个进程运行默认打开三个输入输出流
             * stdin:  重定向，将客户端数据写入文件。（某些题目需要cin）
             * stdout：重定向，输出运行结果
             * stderr: 重定向，输出运行失败异常信息
             */

            else if (pid == 0)
            {
                // 子进程

                // 重定向
                dup2(infd, 0);
                dup2(outfd, 1);
                dup2(errfd, 2);

                // 设置程序时间限制和空间限制
                SetRLimit(cpu_limit, mem_limit);
                

                std::string proc = PathUtil::Exe(proc_name);
                execl(proc.c_str(), proc.c_str(), nullptr);

                LOG(ERROR) << "运行失败,请检查程序是否完成编译" << std::endl;
                close(infd);
                close(outfd);
                close(errfd);
                exit(-3);
            }

            else
            {
                // 父进程

                close(infd);
                close(outfd);
                close(errfd);
                int stat = 0;
                waitpid(pid, &stat, 0);

                LOG(INFO) << "运行结束，进程退出信号: " << (stat & 0x7f) << std::endl;

                return (stat & 0x7f);
            }
        }
    };
}
